import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Scanner;

/**
   Simulator for UI interaction
 */
public class Simulator {
	
	/** Generic Invalid Number */
	private final int INVALID = -1;
	
	/** Clock Start Time */
	private final int START_TIME = 0;
	
	/** Clock Tick Value */
	private final double TICK = 0.1;
	
	/** Indicates if Aging is on or off */
	private boolean aging;

	/** Indicates if Trace is on or off */
	private boolean trace;
	
	/** Value to indicate frequency of Queue*/
	private double displayQueues;

	/** Value to indicate time to pause*/
	private double pauseTime;

	/** Value to indicate clock time*/
	private double clockTime;
	
	/** Scanner to read user input*/
	private Scanner in;
	
	/** Scheduler to handle Ready Queue, CPU and I/O Devices */
	private Scheduler scheduler;
	
	/** 
	   To format double values to one decimal using 
	    getOneDecimal() 
	*/
	private static DecimalFormat df = new DecimalFormat("0.0");
	

	/**
	   Constructs a Simulator with defaults as
	    follows - Aging: Off, Trace: Off,
	    Pause At: NONE, Display Queues Every: NONE.
	 */
	public Simulator()
	{
		aging = false;
		trace = false;
		pauseTime = INVALID;
		displayQueues = INVALID;
		clockTime = START_TIME;
		in = new Scanner(System.in);
		scheduler = new Scheduler();
	}
	
	/**
	   Reads from specified file and loads
	    a scheduler with processes
	   @param file name of file to read and load
	 */
	public void read(String file)
	{
		try 
		{
			// Read File
			FileReader fr = new FileReader(new File(file));
			BufferedReader br = new BufferedReader(fr);
			String currentUnformattedProcess;
			// Read File Line By Line
			while ((currentUnformattedProcess = br.readLine()) != null) 
			{

				String[] pInformation = 
					currentUnformattedProcess.split("[ \t\f\r]+");
				if (pInformation.length == 10)
				{
					Process p = convertToProcess(pInformation);
					//p.printInfo();
					scheduler.addJob(p);
				}
			}
			// Close the readers
			br.close();
			fr.close();
		}
		catch (FileNotFoundException e) 
		{
			System.err.println("Error Opening File: " + e.getMessage());
		}
		catch (IOException e)
		{
			System.err.println(e.getMessage());
		}
	}

	/**
	  	Simulates Process Scheduling
	 */
	private void simulate() 
	{
		while(clockTime <= pauseTime)
		{	
			// Queue Frequency Display
			if (clockTime != START_TIME && 
					(getOneDecimal(clockTime) % displayQueues) == 0)
			{
				System.out.print(getOneDecimal(clockTime) + "\t");
				System.out.println(scheduler.displayQueues());
			}
			
			// notify Scheduler of current clock Time
			scheduler.notify(TICK);
				
			clockTime += TICK; // clock increment simulation
			// adjusts clock time to one decimal
			clockTime = Double.parseDouble(df.format((clockTime)));
		}
		
		clockTime -= TICK;
		// adjusts clock time to one decimal
		clockTime = Double.parseDouble(df.format((clockTime)));
		
		System.out.println(clockTime + "\t\tPause. Continue? (Y/N)");
		if (in.next().equalsIgnoreCase("Y"))
		{
			System.out.println("Current Time: " + clockTime);
			System.out.println("Specific pause time (Greater than current " +
					"time)");
			pauseTime = in.nextDouble();
			pauseTime = Double.parseDouble(df.format((pauseTime)));
			if (pauseTime > clockTime) // reinforce a check
			{	if (trace)
					displayTraceHeader();
				simulate();
			}
		}
	}

	/**
	   Converts a single line String representation of a process
	    from a user input file to an actual Process Object
	   @param process the single line Process description
	   @return Process Object represented by process
	   @throws IOException An exception will be thrown if the Process priority 
	    is out of range [0-15] (inclusive).
	 */
	private Process convertToProcess(String[] pInformation) throws IOException 
	{
		String pid                    = pInformation[0];
		int arrivalTime               = Integer.parseInt(pInformation[1]);
		int cpuTime                   = Integer.parseInt(pInformation[2]);
		double diskRequestProbability = Double.parseDouble(pInformation[3]);
		int averageDiskTime           = Integer.parseInt(pInformation[4]);
		double p1RequestProbability   = Double.parseDouble(pInformation[5]);
		int p1ArrivalTime             = Integer.parseInt(pInformation[6]);
		double p2RequestProbability   = Double.parseDouble(pInformation[7]);
		int p2ArrivalTime             = Integer.parseInt(pInformation[8]);
		int priorityRangeNumber       = Integer.parseInt(pInformation[9]); 
		
		return new Process(pid, arrivalTime, cpuTime, 
				diskRequestProbability, averageDiskTime, 
				p1RequestProbability, p1ArrivalTime,
				p2RequestProbability, p2ArrivalTime,
				priorityRangeNumber);
	}

	/**
	   Creates an Instance of a Scheduler and requests
	    user settings for aging, trace and pause time.
	   @param filename the test filename
	   @throws IOException IO error: cannot write to file
	 */
	public void run(String filename) throws IOException 
	{	
		
		// Request Aging value
		System.out.println("Aging On? (Y/N)");
		if (in.next().equalsIgnoreCase("Y"))
			aging = true;
		scheduler.setAging(aging);
		
		// Request Trace value
		System.out.println("Trace On? (Y/N)");
		if (in.next().equalsIgnoreCase("Y"))
			trace = true;
		scheduler.setTrace(trace);
		
		// Request Pause time
		System.out.println("Pause Time? (Greater than or equal to 0)");
		pauseTime = in.nextDouble();
		pauseTime = Double.parseDouble(df.format((pauseTime)));
		
		// Request Queue Display Frequency
		System.out.println("Display queues every? (in msec)");
		displayQueues = in.nextDouble();
		displayQueues = Double.parseDouble(df.format((displayQueues)));
		
		// Convert File to Process Objects & Load Scheduler (Job Pool)
		read(filename);
		
		// print Simulation header
		System.out.println(displayHeader());
		
		// print Trace Header if On
		if (trace)
			System.out.println(displayTraceHeader());
		
		// Start Simulation
		simulate();
		
		// Print Statistics
		System.out.println(getStatistics());
	}
	
	/**
	   Displays the Simulation Header
	    Ex:         
	    			Simulation
	    Aging: Off
	    Trace: On
	    Display queues every: 45 msec
	    Pause at: 30 msec
	   @return simulation header
	 */
	public String displayHeader()
	{
		String result = "\t\t\tSimulation\n";
		
		result += "Aging: ";
		if (aging) 
			result += "On\n";
		else
			result += "Off\n";
		
		result += "Trace: ";
		if (trace)
			result += "On\n";
		else
			result += "Off\n";
		
		result += "Display queues every: ";
		result += getOneDecimal(displayQueues);
		result += " msec\n";
		
		result += "Pause at: ";
		result += getOneDecimal(pauseTime);
		result += " msec\n";
		
		return result;
	}
	/**
	   Displays the Simulation Header
        Ex:         
       		Trace
	    Time	Event
	    (msec)
	    
	   @return simulation header
	 */
	public String displayTraceHeader()
	{
		String trace = "\t\t\tTrace\n" +
		               "Time\t\tEvent\n" +
		               "(msec)\n";
		return trace;  
	}
	
	/**
	   Returns a String representation of the 
	    average turn around time, average
	    wait time and average response time
	    for the process scheduling simulation
	   @return Statistic Averages 
	 */
	public String getStatistics()
	{
		double totalTurnAround 	 = 0;
		double totalWaitTime     = 0;
		double totalResponseTime = 0;
		ArrayList <Process> complete = scheduler.getCompletedList();
		for (int i = 0; i < complete.size(); i++)
		{
			totalTurnAround   += complete.get(i).getTurnAroundTime();
			totalWaitTime     += complete.get(i).getWaitTime();
			totalResponseTime += complete.get(i).getResponseTime();
		}
		
		totalTurnAround   /= complete.size();
		totalTurnAround    = Double.parseDouble(df.format((totalTurnAround)));
		totalWaitTime     /= complete.size();
		totalWaitTime      = Double.parseDouble(df.format((totalWaitTime)));
		totalResponseTime /= complete.size();
		totalResponseTime  = Double.parseDouble
								(df.format((totalResponseTime)));
		
		String stats = 	"Average Turn Around Time\t= " + 
		  				totalTurnAround +
		  				"\nAverage Wait Time\t\t= " + totalWaitTime +
		  				"\nAverage Response Time\t\t= " + 
		  				totalResponseTime + "\n";
		
		return stats;
	}
	
	/**
	   Returns a double value to one decimal place
	   @param toOneDecimal value to be precision 
	    to one decimal
	   @return double value precision to one decimal
	 */
	public static double getOneDecimal(double toOneDecimal)
	{
		int numberPlusOneDecimal = (int) (toOneDecimal * 10);
		double result            = numberPlusOneDecimal * 0.1;
		String dfResult          = df.format(result);
		result                   = Double.parseDouble(dfResult);
		return result;
	}
}
